﻿using System;
using System.Windows;
using System.Windows.Controls;
using DeepEarth.Map.Core;
using DeepEarth.Core;

namespace DeepEarth.Map.Core.Utilities
{
    public class CoordinateTransformation
    {
        // Mercator Projection Constants:
        const double HALF_PI = 0.159154943091895;
        const double RADIANS_TO_DEGREES = 57.2957795130823;
        const double ScaleX = HALF_PI;
        const double ScaleY = -HALF_PI;
        const double OffsetX = 0.5;
        const double OffsetY = 0.5;
        const double AngularUnitOfMeasurement = 0.017453292519943295D;

        /// <summary>
        /// Converts Map UI coordinates to real world (geographic) coordinates for a point
        /// </summary>
        /// <param name="logicalPoint">The logical UI point</param>
        /// <returns>Geographic Point</returns>
        public static Location LogicalToGeographic(Point logicalPoint)
        {
            return new Location
            {
                Longitude = (logicalPoint.X - OffsetX) * RADIANS_TO_DEGREES / ScaleX,
                Latitude = Math.Atan(Math.Sinh((logicalPoint.Y - OffsetY) / ScaleY)) * RADIANS_TO_DEGREES
            };
        }

        /// <summary>
        /// Converts real world (geographic) point coordinates to Map UI coordinates
        /// </summary>
        /// <param name="geographicPoint"></param>
        /// <returns>Logical Point</returns>
        public static Point GeographicToLogical(Location geographicPoint)
        {
            double d = Math.Sin(geographicPoint.Latitude * AngularUnitOfMeasurement);
            return new Point((geographicPoint.Longitude * AngularUnitOfMeasurement * ScaleX) + OffsetX, (0.5 * Math.Log((1.0 + d) / (1.0 - d)) * ScaleY) + OffsetY);
        }

        /// <summary>
        /// Converts Map UI coordinates to pixel coordinates on screen
        /// </summary>
        /// <param name="logicalPoint"></param>
        /// <param name="msi"></param>
        /// <returns>Screen Pixel</returns>
        public static Point LogicalToPixel(Point logicalPoint, MultiScaleImage msi)
        {
            return msi.LogicalToElementPoint(logicalPoint);
        }

        /// <summary>
        /// Converts pixel coordinates on screen to Map UI coordinates
        /// </summary>
        /// <param name="pixel"></param>
        /// <param name="msi"></param>
        /// <returns>Screen Pixel</returns>
        public static Point PixelToLogical(Point pixel, MultiScaleImage msi)
        {
            return msi.ElementToLogicalPoint(pixel);
        }

        /// <summary>
        /// Converts real world (geographic) point coordinates to pixel coordinates on screen
        /// </summary>
        /// <param name="geographicPoint"></param>
        /// <param name="msi"></param>
        /// <returns>Screen Pixel</returns>
        public static Point GeographicToPixel(Location geographicPoint, MultiScaleImage msi)
        {
            var logical = GeographicToLogical(geographicPoint);
            return LogicalToPixel(logical, msi);
        }

        /// <summary>
        ///  Converts pixel coordinates on screen to real world (geographic) point coordinates
        /// </summary>
        /// <param name="pixel">Pixel Point</param>
        /// <param name="msi"></param>
        /// <returns>Geographical Location</returns>
        public static Location PixelToGeographic(Point pixel, MultiScaleImage msi)
        {
            Point logical = PixelToLogical(pixel, msi);
            return LogicalToGeographic(logical);
        }

        /// <summary>
        /// Converts real world coordinates (in Radians) to pixel coordinates on a world map
        /// </summary>
        /// <param name="latitudeRadians"></param>
        /// <param name="longitudeRadians"></param>
        /// <param name="currentMapScale"></param>
        /// <returns></returns>
        public static Point GeographicToMapPixel(double latitudeRadians, double longitudeRadians, double mapWidth)
        {         
            double x;
            double y;
            double R = mapWidth / (2d * Math.PI);
            double lambda0 = -Math.PI; //-180d / RADIANS_TO_DEGREES;

            double xOnR = longitudeRadians - lambda0;
            x = xOnR * R;

            double top = Math.Sin(latitudeRadians) + 1;
            double bottom = Math.Cos(latitudeRadians);
            double yOnR = Math.Log(top / bottom);

            y = yOnR * R;

            // y is relative to Equator - y=0 is a line on the equator
            // http://mathworld.wolfram.com/MercatorProjection.html

            if (y > 0)
                y = (mapWidth/2) - y;
            else
                y = (mapWidth / 2) + Math.Abs(y);

            return new Point(x, y);
        }

        public static Point GeographicToMapPixel(Location location, double mapWidth)
        {
            return GeographicToMapPixel(location.Latitude / RADIANS_TO_DEGREES, location.Longitude / RADIANS_TO_DEGREES, mapWidth);
        }

        // Wikipedia + Bing Maps control
        const double MercatorMaxLatitude = 85.051128;

        public static Point FindYOffset(IMap map)
        {
            Point p = map.LocationToViewportPoint(new Location(MercatorMaxLatitude, 0));

            double y = Math.Max(0, p.Y);
            return new Point(0, y);
        }
    }
}